/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.tools;
import java.text.*;
import java.util.ArrayList;

import java.awt.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

/**
 * This displays statistics of all data columns in a DataToolTable.
 *
 * @author Douglas Brown
 * @version 1.0
 */
public class DataToolStatsTable extends JTable {
  // instance fields
  DataToolTable dataTable;
  StatsTableModel statsModel;
  DataToolTable.LabelRenderer labelRenderer;  
  NumberRenderer numberRenderer =  new NumberRenderer(3);
	Object[][] statsData; // statsData[col] contains stats for a table column

  /**
   * Constructor.
   *
   * @param table the datatable
   */
  public DataToolStatsTable(DataToolTable table) {
    dataTable = table;
    statsModel = new StatsTableModel();
    init();
  }

  /**
   * Initializes the table.
   */
  protected void init() {
    dataTable.getColumnModel().addColumnModelListener(new
        TableColumnModelListener() {
      public void columnAdded(TableColumnModelEvent e) {/** empty block */}
      public void columnRemoved(TableColumnModelEvent e) {/** empty block */}
      public void columnSelectionChanged(ListSelectionEvent e) {/** empty block */}
      public void columnMarginChanged(ChangeEvent e) {refreshTable();}
      public void columnMoved(TableColumnModelEvent e) {refreshTable();}
    });
    // create statistics data array
    refreshStatistics();
    // set and configure table model and header
    setModel(statsModel);
    setGridColor(Color.blue);
    setTableHeader(null); // no table header
    labelRenderer = dataTable.labelRenderer;
    setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    ListSelectionModel selectionModel = dataTable.getSelectionModel();
    selectionModel.addListSelectionListener(new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent e) {
      	refreshStatistics();
      }
    });
    refreshCellWidths();
  }

  /**
   * Returns labels that describe the statistics.
   *
   * @return labels for max, min, mean, SD, SE and data count
   */
  private String[] getStatLabels() {
  	return new String[] {ToolsRes.getString("Table.Entry.Max"), //$NON-NLS-1$
  				ToolsRes.getString("Table.Entry.Min"), //$NON-NLS-1$
  				ToolsRes.getString("Table.Entry.Mean"), //$NON-NLS-1$
  				ToolsRes.getString("Table.Entry.StandardDev"), //$NON-NLS-1$
  				ToolsRes.getString("Table.Entry.StandardError"), //$NON-NLS-1$
  				ToolsRes.getString("Table.Entry.Count")}; //$NON-NLS-1$
  }
  
  /**
   * Calculates statistical values for a data array.
   *
   * @param data the data array
   * @return the max, min, mean, SD, SE and non-NaN data count
   */
  private Object[] getStatistics(double[] data) {
    double max = -Double.MAX_VALUE;
    double min = Double.MAX_VALUE;
    double sum = 0.0;
    double squareSum = 0.0;
    int count = 0;
    for (int i = 0; i < data.length; i++) {
      if (Double.isNaN(data[i])) {
        continue;
      }
      count++;
      max = Math.max(max, data[i]);
      min = Math.min(min, data[i]);
      sum += data[i];
      squareSum += data[i] * data[i];
    }
    double mean = sum / count;
    double sd = count < 2 ? Double.NaN :
                Math.sqrt((squareSum - count * mean * mean) /
                          (count - 1));
    if (max == -Double.MAX_VALUE) max = Double.NaN;
    if (min == Double.MAX_VALUE) min = Double.NaN;
    return new Object[] {
        new Double(max), new Double(min), new Double(mean), 
        new Double(sd), new Double(sd/Math.sqrt(count)), 
        new Integer(count)};
  }

  /**
   *  Refresh the data display in this table.
   */
  public void refreshTable() {
    Runnable refresh = new Runnable() {
      public synchronized void run() {
        tableChanged(new TableModelEvent(statsModel, TableModelEvent.HEADER_ROW));
        refreshCellWidths();
      }
    };
    if(SwingUtilities.isEventDispatchThread()) {
      refresh.run();
    } else {
      SwingUtilities.invokeLater(refresh);
    }
  }

  /**
   *  Refresh the statistics data.
   */
  public void refreshStatistics() {
    // assemble statistics data array
    TableModel model = dataTable.getModel();
    int[] rows = dataTable.getSelectedRows();
    statsData = new Object[model.getColumnCount()][0];
    ArrayList datalist = new ArrayList();
    // for each column, assemble valid selected data and get stats
    int offset = dataTable.isRowNumberVisible()? 1: 0;
    if (offset == 1) statsData[0] = getStatLabels();
    for (int j = offset; j < model.getColumnCount(); j++) {
    	datalist.clear();
	    for (int i = 0; i < model.getRowCount(); i++) {
	    	Double val = (Double)model.getValueAt(i, j);
	    	if (val == null) val = new Double(Double.NaN);
	    	datalist.add(val);
	    }
      double[] x = new double[datalist.size()];
      for (int i = 0; i < x.length; i++) {
      	x[i] = ((Double)datalist.get(i)).doubleValue();
      }
      double[] selected = x;
      if (rows.length > 0) {
        selected = new double[rows.length];
        for (int i = 0; i < rows.length; i++) {
        	selected[i] = x[rows[i]];
        }
      }
      statsData[j] = getStatistics(selected);
    }
  	refreshTable();
  }
  
  /**
   *  Refresh the cell widths in the table.
   */
  public void refreshCellWidths() {
    // set width of columns
    for (int i = 0; i < getColumnCount(); i++) {
      String name = getColumnName(i);
      TableColumn statColumn = getColumn(name);
      name = dataTable.getColumnName(i);
      TableColumn dataColumn = dataTable.getColumn(name);
      statColumn.setMaxWidth(dataColumn.getWidth());
      statColumn.setMinWidth(dataColumn.getWidth());
      statColumn.setWidth(dataColumn.getWidth());
    }
  }

  /**
   *  Refresh the GUI.
   */
  public void refreshGUI() {
    refreshStatistics();
    refreshTable();
  }
  
  /**
   * Returns the renderer for a cell specified by row and column.
   *
   * @param row the row number
   * @param column the column number
   * @return the cell renderer
   */
  public TableCellRenderer getCellRenderer(int row, int column) {
    int i = dataTable.convertColumnIndexToModel(column);
    if(i==0) return labelRenderer;
    return numberRenderer;
  }

  /**
   * A class to provide model data for this table.
   */
  class StatsTableModel extends AbstractTableModel {

    public String getColumnName(int col) {
      return dataTable.getColumnName(col);
    }

    public int getRowCount() {
      return statsData[0].length;
    }

    public int getColumnCount() {
      return dataTable.getColumnCount();
    }

    public Object getValueAt(int row, int col) {
      int i = dataTable.convertColumnIndexToModel(col);
      return statsData[i][row];
    }

    public boolean isCellEditable(int row, int col) {
      return false;
    }

    public Class getColumnClass(int c) {
      return getValueAt(0, c).getClass();
    }

  }

  /**
   * A class to render numbers and strings.
   */
  class NumberRenderer extends JLabel implements TableCellRenderer {
    NumberFormat format = NumberFormat.getInstance();

    public NumberRenderer(int sigfigs) {
      sigfigs = Math.min(sigfigs, 6);
      if (format instanceof DecimalFormat) {
        String pattern = "0.0"; //$NON-NLS-1$
        for (int i = 0; i < sigfigs - 1; i++) {
          pattern += "0"; //$NON-NLS-1$
        }
        pattern += "E0"; //$NON-NLS-1$
        ((DecimalFormat)format).applyPattern(pattern);
      }
    }

    public Component getTableCellRendererComponent(JTable table, Object value,
                                                   boolean isSelected,
                                                   boolean hasFocus, int row,
                                                   int column) {
      setFont(getDefaultRenderer(String.class).getTableCellRendererComponent(
      			DataToolStatsTable.this, "", false, false, 0, 0).getFont()); //$NON-NLS-1$
      setHorizontalAlignment(SwingConstants.TRAILING);
      if (value instanceof Integer) 
      	setText(String.valueOf(value));
      else setText(format.format(value));
      return this;
    }
  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
